package net.sudot.chess.business.dao;

import net.sudot.chess.base.Chess;
import net.sudot.chess.business.model.BaseEntity;
import net.sudot.commons.pagination.Pageable;
import net.sudot.commons.pagination.QueryCondition;
import org.hibernate.NonUniqueResultException;

import javax.persistence.EntityManager;
import javax.persistence.LockModeType;
import javax.persistence.PersistenceContext;
import javax.persistence.Query;
import javax.persistence.TypedQuery;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaDelete;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Root;
import java.lang.reflect.ParameterizedType;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 基础DAO
 *
 * @author tangjialin on 2018-03-16.
 */
public abstract class BaseDao<E extends BaseEntity> extends Chess {

    @PersistenceContext
    protected EntityManager entityManager;

    /**
     * 实体类型
     */
    protected Class<E> entityClass;

    /**
     * 构造方法
     */
    @SuppressWarnings("unchecked")
    public BaseDao() {
        ParameterizedType type = (ParameterizedType) getClass().getGenericSuperclass();
        this.entityClass = (Class<E>) type.getActualTypeArguments()[0];
    }

    /**
     * 获取实体类型
     *
     * @return Class<E>
     */
    protected Class<E> getEntityClass() {
        return entityClass;
    }

    /**
     * 按条件查询记录是否存在
     *
     * @param attributeName  属性名
     * @param attributeValue 属性值
     * @return true:存在记录,false:没有记录
     */
    public boolean exists(String attributeName, Object attributeValue) {
        CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
        CriteriaQuery<Long> criteriaQuery = criteriaBuilder.createQuery(Long.class);
        Root<E> root = criteriaQuery.from(entityClass);
        criteriaQuery.select(criteriaBuilder.count(root));
        criteriaQuery.where(criteriaBuilder.equal(root.get(attributeName), attributeValue));
        TypedQuery<Long> query = entityManager.createQuery(criteriaQuery);
        return query.getSingleResult() > 0;
    }

    /**
     * 获取指定实体类型的实体对象实例
     *
     * @param entityClass 实体类型
     * @param id          实体主键
     * @return 返回获取的实体对象实例
     */
    protected E find(Class<E> entityClass, Long id) {
        if (id == null) { return null; }
        return entityManager.find(entityClass, id);
    }

    /**
     * 获取实体对象实例
     *
     * @param id 实体主键
     * @return 返回获取的实体对象实例
     */
    public E find(Long id) {
        return find(entityClass, id);
    }

    /**
     * 获取实体对象实例
     *
     * @param attributeName  属性名称
     * @param attributeValue 属性值
     * @return 返回实体对象实例
     */
    public E find(String attributeName, Object attributeValue) {
        CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
        CriteriaQuery<E> criteriaQuery = criteriaBuilder.createQuery(entityClass);
        Root<E> root = criteriaQuery.from(entityClass);
        criteriaQuery.select(root);
        criteriaQuery.where(criteriaBuilder.equal(root.get(attributeName), attributeValue));
        TypedQuery<E> query = entityManager.createQuery(criteriaQuery);
        return uniqueResult(query);
    }

    /**
     * 获取实体对象实例
     *
     * @param attributeName  属性名称
     * @param attributeValue 属性值
     * @return 返回实体对象实例
     */
    public List<E> findList(String attributeName, Object attributeValue) {
        CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
        CriteriaQuery<E> criteriaQuery = criteriaBuilder.createQuery(entityClass);
        Root<E> root = criteriaQuery.from(entityClass);
        criteriaQuery.select(root);
        criteriaQuery.where(criteriaBuilder.equal(root.get(attributeName), attributeValue));
        TypedQuery<E> query = entityManager.createQuery(criteriaQuery);
        return query.getResultList();
    }

    /**
     * 获取唯一的结果集
     * <pre>
     *     JPA的接口实现:{@link org.hibernate.query.internal.AbstractProducedQuery#getSingleResult()},在查询数据为null的情况下会抛出异常
     *     但是大部分时候,业务是允许返回null的,因此使用此方法代替JPA的接口实现
     * </pre>
     *
     * @param list 结果列表
     * @return 返回唯一结果
     */
    protected <E> E uniqueResult(List<E> list) throws NonUniqueResultException {
        int size = list.size();
        if (size == 0) {
            return null;
        }
        E first = list.get(0);
        for (int i = 1; i < size; i++) {
            if (list.get(i) != first) {
                throw new NonUniqueResultException(list.size());
            }
        }
        return first;
    }

    /**
     * 获取唯一的结果集
     * <pre>
     *     JPA的接口实现:{@link org.hibernate.query.internal.AbstractProducedQuery#getSingleResult()},在查询数据为null的情况下会抛出异常
     *     但是大部分时候,业务是允许返回null的,因此使用此方法代替JPA的接口实现
     * </pre>
     *
     * @param query 查询执行
     * @return 返回唯一结果
     */
    @SuppressWarnings("unchecked")
    protected <E> E uniqueResult(Query query) throws NonUniqueResultException {
        query.setMaxResults(1);
        return uniqueResult((List<E>) query.getResultList());
    }

    /**
     * 获取所有实体
     *
     * @param entityClass 实体类型
     * @return 返回所有指定实体类型的实体对象实例集合
     */
    protected List<E> findAll(Class<E> entityClass) {
        String hql = "SELECT _table FROM " + entityClass.getName()
                + " AS _table ORDER BY _table.id ASC";
        return entityManager.createQuery(hql, entityClass).getResultList();
    }

    /**
     * 获取所有实体
     *
     * @return 返回所有实体对象实例集合
     */
    public List<E> findAll() {
        return findAll(entityClass);
    }

    /**
     * 保存实体
     *
     * @param e 期望添加的实体对象实例
     * @return 返回添加后的实体对象实例
     */
    public E persist(E e) {
        entityManager.persist(e);
        return e;
    }

    /**
     * 修改实体
     *
     * @param e 期望修改的实体对象实例
     * @return 返回修改后的实体对象实例
     */
    public E merge(E e) {
        e.setDeleted(false);
        e = entityManager.merge(e);
        return e;
    }

    /**
     * 移除实体对象
     *
     * @param id 实体对象主键
     */
    public void remove(Long id) {
        if (id == null) { return; }
        CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
        CriteriaDelete<E> criteriaDelete = criteriaBuilder.createCriteriaDelete(entityClass);
        Root<E> root = criteriaDelete.from(entityClass);
        criteriaDelete.where(criteriaBuilder.equal(root.get("id"), id));
        entityManager.createQuery(criteriaDelete).executeUpdate();
    }

    /**
     * 移除实体对象
     *
     * @param attributeName  属性名称
     * @param attributeValue 属性值
     */
    public void remove(String attributeName, Object attributeValue) {
        CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
        CriteriaDelete<E> criteriaDelete = criteriaBuilder.createCriteriaDelete(entityClass);
        Root<E> root = criteriaDelete.from(entityClass);
        criteriaDelete.where(criteriaBuilder.equal(root.get(attributeName), attributeValue));
        entityManager.createQuery(criteriaDelete).executeUpdate();
    }

    /**
     * 移除实体对象
     *
     * @param entity 实体对象
     */
    public void remove(E entity) {
        if (entity == null) { return; }
        entityManager.remove(entity);
    }

    /**
     * 刷新实体对象
     *
     * @param entity 实体对象
     */
    public void refresh(E entity) {
        if (entity == null) { return; }
        entityManager.refresh(entity);
    }

    /**
     * 刷新实体对象
     *
     * @param entity       实体对象
     * @param lockModeType 锁定方式
     */
    public void refresh(E entity, LockModeType lockModeType) {
        if (entity == null) { return; }
        if (lockModeType == null) {
            entityManager.refresh(entity);
        } else {
            entityManager.refresh(entity, lockModeType);
        }
    }

    /**
     * 获取锁定方式
     *
     * @param entity 实体对象
     * @return 锁定方式
     */
    public LockModeType getLockMode(E entity) {
        return entityManager.getLockMode(entity);
    }

    /**
     * 清除缓存
     */
    public void clear() {
        entityManager.clear();
    }

    /**
     * 同步数据
     */
    public void flush() {
        entityManager.flush();
    }


    /**
     * 分页设置
     *
     * @param q        查询对象
     * @param pageNum  页码
     * @param pageSize 每一页显示记录数
     * @return 返回查询对象
     */
    protected Query settingPagination(Query q, int pageNum, int pageSize) {
        if (pageNum > 0 && pageSize > 0) {
            q.setMaxResults(pageSize).setFirstResult((pageNum - 1) * pageSize);
        }
        return q;
    }

    /**
     * 分页设置
     *
     * @param query    查询对象
     * @param pageable 分页参数
     * @return 返回查询对象
     */
    protected Query settingPagination(Query query, Pageable pageable) {
        return settingPagination(query, pageable.getPage(), pageable.getRows());
    }

    /**
     * 封装动态查询条件
     *
     * @param ql       查询语句,并通过此语句返回最终查询语句
     * @param extQl    扩展查询语句
     * @param pageable 分页请求抽象接口
     * @param list     参数值集合
     * @return 返回组装好的查询参数, 并通过ql返回最终查询语句
     */
    protected List<Object> dynamicCondition(StringBuilder ql, String extQl, Pageable pageable, List<Object> list) {
        List<QueryCondition> queryConditions = pageable.getQueryConditions();
        if (queryConditions != null && !queryConditions.isEmpty()) {
            list = list == null ? new ArrayList<>(queryConditions.size()) : list;
            for (int i = 0; i < queryConditions.size(); i++) {
                ql.append(" AND ");
                QueryCondition query = queryConditions.get(i);
                String tableName = query.getTableName();
                if (tableName != null && !tableName.isEmpty()) {
                    ql.append(tableName).append(".");
                }
                ql.append(query.getColumnName()).append(" ").append(query.getOperator()).append(" ?");
                list.add(query.getValue());
            }
        }
        if (extQl != null) {
            ql.append(" ").append(extQl);
        }
        return list;
    }

    /**
     * 封装动态查询条件
     *
     * @param ql        查询语句,并通过此语句返回最终查询语句
     * @param extQl     扩展查询语句
     * @param pageable  分页请求抽象接口
     * @param condition 参数值集合
     * @return 返回组装好的查询参数, 并通过ql返回最终查询语句
     */
    protected Map<String, Object> dynamicCondition(StringBuilder ql, String extQl, Pageable pageable, Map<String, Object> condition) {
        List<QueryCondition> queryConditions = pageable.getQueryConditions();
        if (queryConditions != null && !queryConditions.isEmpty()) {
            condition = condition == null ? new HashMap<String, Object>(queryConditions.size()) : condition;
            for (int i = 0; i < queryConditions.size(); i++) {
                ql.append(" AND ");
                QueryCondition query = queryConditions.get(i);
                String tableName = query.getTableName();
                if (tableName != null && !tableName.isEmpty()) {
                    ql.append(tableName).append(".");
                }
                String key = query.getColumnName() + "_" + i;
                ql.append(query.getColumnName()).append(" ").append(query.getOperator()).append(":").append(key);
                condition.put(key, query.getValue());
            }
        }
        if (extQl != null) {
            ql.append(" ").append(extQl);
        }
        return condition;
    }

    /**
     * 封装动态查询条件
     *
     * @param ql       查询语句,并通过此语句返回最终查询语句
     * @param extQl    扩展查询语句
     * @param pageable 分页请求抽象接口
     * @param values   参数值集合
     * @return 返回组装好的查询参数, 并通过ql返回最终查询语句
     */
    protected Object[] dynamicCondition(StringBuilder ql, String extQl, Pageable pageable, Object... values) {
        List<QueryCondition> queryConditions = pageable.getQueryConditions();
        if (queryConditions != null && !queryConditions.isEmpty()) {
            int len = values == null ? 0 : values.length;
            int size = queryConditions.size();
            Object[] os = new Object[len + size];
            for (int i = 0; i < len; i++) {
                os[i] = values[i];
            }
            for (int i = 0; i < size; i++) {
                ql.append(" AND ");
                QueryCondition query = queryConditions.get(i);
                String tableName = query.getTableName();
                if (tableName != null && !tableName.isEmpty()) {
                    ql.append(tableName).append(".");
                }
                ql.append(query.getColumnName()).append(" ").append(query.getOperator()).append(" ?");
                os[len + i] = query.getValue();
            }
            values = os;
        }
        if (extQl != null) {
            ql.append(" ").append(extQl);
        }
        return values;
    }

}